package com.googlecode.alfrescocachingserver.services.impl;

import java.util.HashSet;
import java.util.Set;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.TransactionController;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.webscripts.DeclarativeRegistry;
import org.springframework.extensions.webscripts.Match;

import com.googlecode.alfrescocachingserver.exceptions.ServiceException;
import com.googlecode.alfrescocachingserver.extractor.CacheIdExtractor;
import com.googlecode.alfrescocachingserver.model.CacheWebScript;
import com.googlecode.alfrescocachingserver.model.WebScriptCacheKey;
import com.googlecode.alfrescocachingserver.model.WebScriptResponse;
import com.googlecode.alfrescocachingserver.model.WebScriptUri;
import com.googlecode.alfrescocachingserver.services.CacheKeysMappingService;

public class CacheKeysMappingServiceImpl implements CacheKeysMappingService
{
    private static final Logger LOG = LoggerFactory.getLogger(CacheKeysMappingServiceImpl.class);

    private static final String CACHE_NAME = "AlfrescoIdCacheKeysMapping";
    
    protected CacheManager cacheManager;
    protected DeclarativeRegistry uriRegistry;

    public void setCacheManager(CacheManager cacheManager)
    {
        this.cacheManager = cacheManager;
    }

    public void setUriRegistry(DeclarativeRegistry uriRegistry)
    {
        this.uriRegistry = uriRegistry;
    }

    @Override
    public Set<WebScriptCacheKey> getCacheKeysById(String id)
    {
//        TransactionDebugUtils.transactionRequired("CacheKeysMappingServiceImpl.updateMapping()");

        Cache cache = cacheManager.getCache(CACHE_NAME);

        TransactionController transactionController = cacheManager.getTransactionController();
        transactionController.begin();

        try
        {
            Element element = cache.get(id);
    
            transactionController.commit();
            
            if (element != null)
            {
                @SuppressWarnings("unchecked")
                Set<WebScriptCacheKey> cacheKeySet = (Set<WebScriptCacheKey>)element.getObjectValue();
                
                return cacheKeySet;
            }
            
            return new HashSet<WebScriptCacheKey>();
        }
        catch (Exception e)
        {
            transactionController.rollback();
            throw new ServiceException(e);
        }
    }

    @Override
    public void updateMapping(WebScriptCacheKey webScriptCacheKey, WebScriptResponse webScriptResponse)
    {
//        TransactionDebugUtils.transactionRequired("CacheKeysMappingServiceImpl.updateMapping()");

        Set<String> nodeIds = new HashSet<String>();
        
        WebScriptUri webScriptUri = webScriptCacheKey.getWebScriptUri();
        Match match = uriRegistry.findWebScript(webScriptUri.getHttpMethod().name(), webScriptUri.getUri());
        
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Cache key mapping template = {}", match.getTemplate());
            LOG.debug("Cache key mapping template vars = {}", match.getTemplateVars());
        }
        
        CacheWebScript cacheWebScript = (CacheWebScript)match.getWebScript();
        
        CacheIdExtractor cacheIdExtractor = cacheWebScript.getCacheIdExtractor();
        
        if (cacheIdExtractor != null)
        {
            nodeIds = cacheIdExtractor.retrieveIds(webScriptCacheKey, webScriptResponse);
        }

        TransactionController transactionController = cacheManager.getTransactionController();
        
        transactionController.begin();

        try
        {
            Cache cache = cacheManager.getCache(CACHE_NAME);
            
            for (String nodeId : nodeIds)
            {
                createUpdateMapping(cache, nodeId, webScriptCacheKey);
            }
            
            transactionController.commit();
        }
        catch (Exception e)
        {
            transactionController.rollback();
            throw new ServiceException(e);
        }
    }
    
    private void createUpdateMapping(Cache cache, String id, WebScriptCacheKey webScriptCacheKey)
    {
        Element element = cache.get(id);
        Set<WebScriptCacheKey> cacheKeySet = null;
        
        if (element == null)
        {
            cacheKeySet = new HashSet<WebScriptCacheKey>();
            
            element = new Element(id, cacheKeySet);
        }
        else
        {
            cacheKeySet = (Set<WebScriptCacheKey>)element.getObjectValue();
        }

        cacheKeySet.add(webScriptCacheKey);
        
        if (LOG.isDebugEnabled())
        {
            StringBuilder buf = new StringBuilder();
            
            for (WebScriptCacheKey cacheKey : cacheKeySet)
            {
                buf.append(cacheKey.getWebScriptUri().getUriWithQueryString()).append(", ");
            }
            
            buf.deleteCharAt(buf.length() - 1);
            
            LOG.debug("Updated cache key mapping:  id = {}, values = {}", id, buf.toString());
        }
        
        // update or create the ID to cache keys mapping entry
        cache.put(element);
    }
}
